package com.ficus.road.maintain.service;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.ficus.road.maintain.core.model.*;
import com.ficus.road.maintain.dao.*;
import com.ficus.road.maintain.dto.*;
import com.ficus.road.maintain.util.*;
import com.ficus.road.maintain.util.excel.ExcelUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.http.HttpStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.io.File;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author xiawen yang
 * @date 2021/11/15 下午4:57
 */
@Service
@Slf4j
public class MonitorService {

    private static Map<String, Car> carMap = new HashMap<>();
    private static Map<String, Company> companyMap = new HashMap<>();
    private static String authStr;
    private static String dateStr;
    private static String tomorrowDateStr;
    private static String yesterdayDateStr;
    @Autowired
    private HitDao hitDao;
    @Autowired
    private ChecklistDao checklistDao;
    @Autowired
    private CarDao carDao;
    @Autowired
    private CarVideoRecordDao carVideoRecordDao;
    @Autowired
    private OperationLogDao operationLogDao;
    @Autowired
    private CompanyDao companyDao;
    @Autowired
    private AlertDao alertDao;
    @Autowired
    private CarMaintainDao carMaintainDao;
    @Autowired
    private CarTripDao carTripDao;
    @Value("${monitor.url}")
    private String url;
    @Value("${monitor.username}")
    private String username;
    @Value("${monitor.password}")
    private String password;
    @Value("${monitor.dirPath}")
    private String dirPath;
    @Value("${monitor.dingTalk.tokenId}")
    private String tokenId;
    @Value("${monitor.dingTalk.secret}")
    private String secret;
    @Value("${monitor.downloadUrl}")
    private String downloadUrl;
    @Value("${monitor.recount}")
    private boolean recount;
    private List<String> blackListAlertName = Arrays.asList("CarSystemDiskLow", "SdkDown", "GpuMonitorDown", "NodeMonitorDown"
            , "DockerMonitorDown", "ServerSystemDiskLow", "CarStorageDiskLow", "SystemMemoryLow", "CarMemoryLow", "CarSystemDiskUsedHight"
            , "GpsLatitudeIllegal", "GpsLongitudeIllegal");

    @PostConstruct
    void init() {
        Date date = new Date();
        dateStr = MyDateUtil.dateFormatStr(date, "yyyy-MM-dd");
        yesterdayDateStr = MyDateUtil.dateFormatStr(DateUtils.addDays(date, -1), "yyyy-MM-dd");
        tomorrowDateStr = MyDateUtil.dateFormatStr(DateUtils.addDays(date, 1), "yyyy-MM-dd");
        authStr = getAuthorization(username, password);
        List<Car> carList = carDao.findAll();
        carMap = carList.stream().collect(Collectors.toMap(Car::getId, Function.identity()));
        List<Company> companyList = companyDao.findAll();
        companyMap = companyList.stream().collect(Collectors.toMap(Company::getId, Function.identity()));
    }

    /**
     * 每天零点重置日期, 车辆列表, 初始化报表
     */
//    @Scheduled(cron = "0 1 0 * * ?")
    public void reset() {
        Date date = new Date();
        dateStr = MyDateUtil.dateFormatStr(date, "yyyy-MM-dd");
        yesterdayDateStr = MyDateUtil.dateFormatStr(DateUtils.addDays(date, -1), "yyyy-MM-dd");
        tomorrowDateStr = MyDateUtil.dateFormatStr(DateUtils.addDays(date, 1), "yyyy-MM-dd");
        Checklist initChecklist = initChecklist();
        checklistDao.saveOrUpdate(initChecklist);
        log.info("巡检报表初始化成功");
    }

    /**
     * sdk端出车统计
     *
     * @param checklistOfCar
     */
    private void calculateSDKStatus(ChecklistOfCar checklistOfCar, Map<String, String> headers, Map<String, String> params) {
        String queryStr = "up{instance='" + checklistOfCar.getCarId() + "', job='car_status', type='cadvisor'}[1d]";
        params.put("query", queryStr);
        HttpClientUtil.HttpClientResult clientResult = HttpClientUtil.doGet(url, headers, params);
        if (clientResult.getCode() == HttpStatus.SC_OK) {
            JSONObject responseData = JSONObject.parseObject(clientResult.getContent()).getJSONObject("data");
            JSONArray resultArray = responseData.getJSONArray("result");
            if (ObjectUtils.isNotEmpty(resultArray)) {
                int upMinute = 0;
                JSONObject result = resultArray.getJSONObject(0);
                JSONArray values = result.getJSONArray("values");
                for (int i = 0; i < values.size(); i++) {
                    JSONArray value = (JSONArray) values.get(i);
                    String status = (String) value.get(1);
                    if (status.equals("1")) {
                        upMinute++;
                    }
                }
                if (upMinute > 0) {
                    // 更新出车状态：1出车 0未出车
                    checklistOfCar.setSdkCarStatus(1);
                }
                // 更新出车时长
                checklistOfCar.setSdkCarTotalMinute(upMinute);
            }
        }
    }

    /**
     * 硬件状态统计
     *
     * @param checklistOfCar
     */
    private void calculateHardwareStatus(ChecklistOfCar checklistOfCar) {
        List<AlertDTO> alertDTOS = alertDao.findByQuery(new Query(Criteria.where("car_id").is(checklistOfCar.getCarId()).and("date").is(dateStr)));
        if (ObjectUtils.isNotEmpty(alertDTOS)) {
            alertDTOS.stream().forEach(alertDTO -> {
                switch (alertDTO.getAlertname()) {
                    case GNSSError:
                        checklistOfCar.setIsGNSSError(1);
                        checklistOfCar.setGNSSErrorTimes(alertDTO.getTimes());
                        break;
                    case LidarError:
                        checklistOfCar.setIsLidarError(1);
                        checklistOfCar.setLidarErrorTimes(alertDTO.getTimes());
                        break;
                    case CameraError:
                        checklistOfCar.setIsCameraError(1);
                        checklistOfCar.setCameraErrorTimes(alertDTO.getTimes());
                        break;
                }
            });
        }
    }

    //todo: 车保养后且第二天没出车，那么报表显示的数据是保养前的最后一个有效数据
    private void calculateHardwareCap(ChecklistOfCar checklistOfCar, Map<String, String> headers, Map<String, String> params) {
        String carId = checklistOfCar.getCarId();
        for (int day = 1; day <= 30; day++) {
            String queryParam = "node_filesystem_avail_bytes{job=\"car_status\",mountpoint=\"/mnt/storage\", instance=\'" + carId + "\'}[" + day + "d]";
            params.put("query", queryParam);
            HttpClientUtil.HttpClientResult clientResult = HttpClientUtil.doGet(url, headers, params);
            if (clientResult.getCode() == HttpStatus.SC_OK) {
                JSONObject responseData = JSONObject.parseObject(clientResult.getContent()).getJSONObject("data");
                JSONArray resultArray = responseData.getJSONArray("result");
                if (ObjectUtils.isNotEmpty(resultArray)) {
                    JSONObject result = resultArray.getJSONObject(0);
                    JSONArray values = result.getJSONArray("values");
                    JSONArray value = values.getJSONArray(values.size() - 1);
                    String capacityStr = value.getString(1);
                    Double capacity = Double.parseDouble(capacityStr);
                    capacity = capacity / (1024 * 1024 * 1024);
                    Double capG = FormatUtil.keepDecimalsOfK(capacity, 2);
                    checklistOfCar.setStorageDiskCap(capG);
                    break;
                } else if (!checklistOfCar.getStorageDiskCap().equals(0d)) {
                    // 查不到历史数据但是数据库中数据不为0，直接跳出循环
                    break;
                }
                // 查不到历史数据，数据库中数据为0，则扩大查找范围，继续查询历史数据
            }
        }
    }

    /**
     * 车辆监测
     */
//    @Scheduled(cron = "0 0 22 * * ?")
    public void carMonitor() {
        log.info("车辆监测统计开始");
        long start = System.currentTimeMillis();
        Map<String, String> headers = new HashMap<>();
        headers.put("Authorization", authStr);
        Map<String, String> params = new HashMap<>();

        Date date = new Date();
        Checklist fetch = checklistDao.fetch(dateStr);
        List<ChecklistOfCar> carCheckList = fetch.getCarList();

        //出车统计
        Date startOfDay = MyDateUtil.getStartOfDay(date);
        Criteria criteria = Criteria.where("start_time").gte(startOfDay.getTime());
        Query query = new Query(Criteria.where("date").is(MyDateUtil.dateFormatStr(date, "yyyyMMdd")));
        List<CarVideoRecord> carVideoRecords = carVideoRecordDao.findByQuery(query);
        Map<String, List<CarVideoRecord>> carVideoMap = carVideoRecords.stream().collect(Collectors.groupingBy(CarVideoRecord::getCarId));

        carCheckList.stream().forEach(checklistOfCar -> {
            //统计sdk出车时长、sdk端出车状态
            calculateSDKStatus(checklistOfCar, headers, params);
            //统计硬件状态
            calculateHardwareStatus(checklistOfCar);
            //查询数据盘容量
            calculateHardwareCap(checklistOfCar, headers, params);
            //出车统计
            List<CarVideoRecord> carVideoList = carVideoMap.get(checklistOfCar.getCarId());
            if (ObjectUtils.isNotEmpty(carVideoList)) {
                Long endTime = Long.MIN_VALUE;
                Long startTime = Long.MAX_VALUE;
                //云端出车(1出车 0未出车)
                checklistOfCar.setCloudCarStatus(1);
                int cloudCarTotalMinute = 0;
                //出车时间
                for (CarVideoRecord carVideoRecord : carVideoList) {
                    endTime = Math.max(endTime, carVideoRecord.getEndTime());
                    startTime = Math.min(startTime, carVideoRecord.getStartTime());
                    cloudCarTotalMinute += carVideoRecord.getEndTime() - carVideoRecord.getStartTime();
                }
                checklistOfCar.setStartTime(MyDateUtil.dateFormatStr(new Date(startTime), "HH:mm:ss"));
                checklistOfCar.setEndTime(MyDateUtil.dateFormatStr(new Date(endTime), "HH:mm:ss"));
                //出车总时长
                checklistOfCar.setCloudCarTotalMinute(cloudCarTotalMinute / 60000);
            }
            //出车状态统计, 1故障 0未故障
            checklistOfCar.setIsCarOutStatusError(checklistOfCar.getSdkCarStatus() ^ checklistOfCar.getCloudCarStatus());
            Car car = carMap.get(checklistOfCar.getCarId());
            //版本号
            checklistOfCar.setVersion(car.getVersion());
            //里程
            Query carTripQuery = new Query(Criteria.where("car_id").is(checklistOfCar.getCarId()).and("timestamp").gte(startOfDay.getTime()));
            List<CarTrip> carTripList = carTripDao.findByQuery(carTripQuery);
            double totalMiles = carTripList.stream().mapToDouble(CarTrip::getMile).sum();
            checklistOfCar.setMiles(FormatUtil.keepDecimalsOfK(totalMiles, 2));
        });
        checklistDao.saveOrUpdate(fetch);
        log.info("车辆监测统计耗时： {} ms", System.currentTimeMillis() - start);
    }

    /**
     * 业务监测
     */
//    @Scheduled(cron = "* 10 22 * * ?")
    public void businessMonitor() {
        log.info("业务监测统计开始");
        long start = System.currentTimeMillis();
        Date date = new Date();
        Checklist fetch = checklistDao.fetch(dateStr);
        List<ChecklistOfCar> carCheckList = fetch.getCarList();

        Date startOfDay = MyDateUtil.getStartOfDay(date);
        //病害merge数统计
        Query mergeQuery = new Query(Criteria.where("list.1").exists(true).and("list").elemMatch(Criteria.where("hit_time").gte(startOfDay.getTime() / 1000)));
        List<Hit> mergeQueryHits = hitDao.findByQuery(mergeQuery);
        Map<String, List<Hit>> mergeHitsMap = mergeQueryHits.stream().collect(Collectors.groupingBy(hit -> hit.getAttrs().getCarId()));
        //当日病害数统计
        Query query = new Query(Criteria.where("first_hit_time").gte(startOfDay.getTime() / 1000).and("attrs.confirmation.status").nin(Confirmation.ConfirmationStatus.DELETE));
        List<Hit> hits = hitDao.findByQuery(query);
        Map<String, List<Hit>> newHitsMap = hits.stream().collect(Collectors.groupingBy(i -> i.getAttrs().getCarId()));
        //推送中海数（成功数）
        Query pushQuery = new Query(Criteria.where("title").is("推送中海").and("type").is("info").and("operate_date").gte(MyDateUtil.getStartOfDay(new Date())));
        List<OperationLog> pushList = operationLogDao.findByQuery(pushQuery);
        //总病害数统计
        Checklist yesterdayFetch = checklistDao.fetch(yesterdayDateStr);
        List<ChecklistOfCar> yesterdayCarList = null;
        if (ObjectUtils.isNotEmpty(yesterdayFetch)) {
            yesterdayCarList = yesterdayFetch.getCarList();
        }
        List<ChecklistOfCar> yCarList = yesterdayCarList;
        carCheckList.stream().forEach(checklistOfCar -> {
            //merge病害数统计
            List<Hit> mergeHitsList = mergeHitsMap.get(checklistOfCar.getCarId());
            if (ObjectUtils.isNotEmpty(mergeHitsList)) {
                checklistOfCar.setMergeNum((long) mergeHitsList.size());
            }
            //当日病害数统计
            List<Hit> newHitsList = newHitsMap.get(checklistOfCar.getCarId());
            long hitNum = 0;
            long passNum = 0;
            if (ObjectUtils.isNotEmpty(newHitsList)) {
                hitNum = newHitsList.size();
                long uncheckHitNum = newHitsList.stream().filter(hit -> hit.getAttrs().getConfirmation().getStatus().equals(Confirmation.ConfirmationStatus.TO_BE_CONFIRMED)).count();
                passNum = newHitsList.stream().filter(hit -> hit.getAttrs().getConfirmation().getStatus().equals(Confirmation.ConfirmationStatus.CORRECT)).count();
                double dHitNum = (double) hitNum;
                double dUncheckHitNum = (double) uncheckHitNum;
                double dPassNum = (double) passNum;
                //发现新病害数(包括标注的)
                checklistOfCar.setHitNum(hitNum);
                //未审核数
                checklistOfCar.setUncheckHitNum(uncheckHitNum);
                //审核率
                checklistOfCar.setCheckRate(hitNum == 0 ? 0 : FormatUtil.keepDecimalsOfK((dHitNum - dUncheckHitNum) / dHitNum, 2));
                //审核通过数
                checklistOfCar.setPassHitNum(passNum);
                //通过率(包括标注的病害)
                checklistOfCar.setPassRate(hitNum == 0 ? 0 : FormatUtil.keepDecimalsOfK(dPassNum / dHitNum, 2));
            }
            //推送中海数统计
            if (ObjectUtils.isNotEmpty(pushList)) {
                long pushCount = pushList.stream().filter(operationLog -> {
                    JSONArray jsonArray = JSONArray.parseArray(operationLog.getParams());
                    for (int i = 0; i < jsonArray.size(); i++) {
                        JSONObject jsonObject = jsonArray.getJSONObject(i);
                        if (ObjectUtils.isNotEmpty(jsonObject)) {
                            JSONObject iddr = jsonObject.getJSONObject("iddr");
                            if (ObjectUtils.isNotEmpty(iddr)) {
                                JSONObject info = iddr.getJSONObject("info");
                                if (ObjectUtils.isNotEmpty(info)) {
                                    String carId = info.getString("devid");
                                    if (checklistOfCar.getCarId().equals(carId)) {
                                        return true;
                                    }
                                }
                            }
                        }
                    }
                    return false;
                }).count();
                checklistOfCar.setPushThirdNum(pushCount);
            }
            long yTotalYHitNum = 0, yTotalYPassHitNum = 0;
            if (recount) {
                //统计当日之前的总病害
                Query totalHitQuery = new Query(Criteria.where("first_hit_time").lte(startOfDay.getTime() / 1000).and("attrs.confirmation.status").nin(Confirmation.ConfirmationStatus.DELETE).and("attrs.car_id").is(checklistOfCar.getCarId()));
                yTotalYHitNum = hitDao.count(totalHitQuery);
                Query totalPassQuery = new Query(Criteria.where("first_hit_time").lte(startOfDay.getTime() / 1000).and("attrs.confirmation.status").is(Confirmation.ConfirmationStatus.CORRECT).and("attrs.car_id").is(checklistOfCar.getCarId()));
                yTotalYPassHitNum = hitDao.count(totalPassQuery);
            } else {
                ChecklistOfCar yesterdayData = yCarList.stream().filter(yChecklistOfCar -> yChecklistOfCar.getCarId().equals(checklistOfCar.getCarId())).findFirst().get();
                yTotalYHitNum = yesterdayData.getTotalNewHitNum();
                yTotalYPassHitNum = yesterdayData.getTotalPassHitNum();
            }
            //总病害数
            checklistOfCar.setTotalNewHitNum(yTotalYHitNum + hitNum);
            //总审核通过数
            checklistOfCar.setTotalPassHitNum(yTotalYPassHitNum + passNum);
        });
        checklistDao.saveOrUpdate(fetch);
        log.info("业务监测统计耗时： {} ms", System.currentTimeMillis() - start);
    }

    /**
     * 事件监测、总数统计
     */
//    @Scheduled(cron = "0 20 22 * * ?")
    public void eventMonitor() {
        log.info("事件监测统计开始");
        long start = System.currentTimeMillis();
        Checklist fetch = checklistDao.fetch(dateStr);
        Date date = DateUtils.addDays(new Date(), -1);
        Date startOfDay = MyDateUtil.getStartOfDay(date);
        List<ChecklistOfEvent> checklistOfEvents = new ArrayList<>();
        Query query = new Query(Criteria.where("first_hit_time").gte(startOfDay.getTime() / 1000));
        List<Hit> allHits = hitDao.findByQuery(query);
        Map<String, List<Hit>> hitTypeMap = allHits.stream().collect(Collectors.groupingBy(i -> i.getType()));
        //总数统计: 总检出病害(发现病害), 总展示病害(审核通过的病害)
        TotalStatistic totalStatistic = new TotalStatistic();
        int totalToday = (int) hitDao.count(new Query(Criteria.where("attrs.source").is(SourceEnum.NORMAL.toString()).and("first_hit_time").gte(MyDateUtil.getStartOfDay(new Date()).getTime() / 1000)));
        int totalCorrectToday = (int) hitDao.count(new Query(Criteria.where("attrs.source").is(SourceEnum.NORMAL.toString())
                .and("attrs.confirmation.status").is(Confirmation.ConfirmationStatus.CORRECT).and("first_hit_time").gte(MyDateUtil.getStartOfDay(new Date()).getTime() / 1000)));
        totalStatistic.setTotalNewHits(totalToday);
        totalStatistic.setTotalCheckHits(totalCorrectToday);
        //总数统计: 总里程数
        double totalMiles = fetch.getCarList().stream().mapToDouble(ChecklistOfCar::getMiles).sum();
        totalStatistic.setTotalMiles(FormatUtil.keepDecimalsOfK(totalMiles, 2));
        //总数统计: 总运行时间
        int totalRuntime = fetch.getCarList().stream().mapToInt(ChecklistOfCar::getCloudCarTotalMinute).sum();
        totalStatistic.setTotalRuntime(FormatUtil.keepDecimalsOfK((double) totalRuntime / 60, 2));
        fetch.setTotalStatistic(totalStatistic);

        hitTypeMap.forEach((type, hits) -> {
            int total = hits.size();
            int tagHitNum = (int) hits.stream().filter(hit -> hit.getAttrs().getSource().equals("TAG")).count();
            int normalHitNum = (int) hits.stream().filter(hit -> hit.getAttrs().getSource().equals("NORMAL")).count();
            int totalPassHitNum = (int) hits.stream().filter(hit -> hit.getAttrs().getConfirmation().getStatus().equals(Confirmation.ConfirmationStatus.CORRECT) &&
                    hit.getAttrs().getSource().equals(SourceEnum.NORMAL.toString())).count();
            int uncheckHitNum = (int) hits.stream().filter(hit -> hit.getAttrs().getConfirmation().getStatus().equals(Confirmation.ConfirmationStatus.TO_BE_CONFIRMED)).count();
            double checkRate = total == 0 ? 0 : FormatUtil.keepDecimalsOfK((double) (total - uncheckHitNum) / (double) total, 2);
            // 百公里新增病害数(总里程数为0时不统计百公里病害数)
            int newOfHMile = totalMiles == 0 ? 0 : (int) (normalHitNum * 100 / totalMiles);
            // 百公里审核通过数(总里程数为0时不统计百公里审核通过数)
            int passOfHMile = totalMiles == 0 ? 0 : (int) (totalPassHitNum * 100 / totalMiles);
            //准确率 = normal类型且审核通过的病害 / normal类型的总病害数
            int passHitNum = (int) hits.stream().filter(hit -> hit.getAttrs().getConfirmation().getStatus().equals(Confirmation.ConfirmationStatus.CORRECT) &&
                    hit.getAttrs().getSource().equals(SourceEnum.NORMAL.toString())).count();
            double passRate = normalHitNum == 0 ? 0 : FormatUtil.keepDecimalsOfK((double) passHitNum / (double) normalHitNum, 2);
            ChecklistOfEvent checklistOfEvent = ChecklistOfEvent.builder().eventType(type).total(total).tagHitNum(tagHitNum).normalHitNum(normalHitNum)
                    .passHitNum(totalPassHitNum).uncheckHitNum(uncheckHitNum).checkRate(checkRate)
                    .newHitNumOfHMile(newOfHMile).passHitNumOfHMile(passOfHMile).passRate(passRate).build();
            checklistOfEvents.add(checklistOfEvent);
        });
        fetch.setEventList(checklistOfEvents);
        checklistDao.saveOrUpdate(fetch);
        log.info("时间监测统计耗时： {} ms", System.currentTimeMillis() - start);
    }

    /**
     * 异常统计、异常告警、异常分布、推送统计
     */
//    @Scheduled(cron = "0 30 22 * * ?")
    public void statisticOnStatistic() {
        log.info("推送统计开始");
        long start = System.currentTimeMillis();
        Checklist fetch = checklistDao.fetch(dateStr);
        List<ChecklistOfCar> carCheckList = fetch.getCarList();
        PushStatistic pushStatistic = fetch.getPushStatistic();

        //异常统计
        double carOutNum = 0, carErrorNum = 0;
        List<CarAlertInfo> carAlertInfoList = new ArrayList<>();
        int carStatusError = 0, gnssError = 0, lidarError = 0, cameraError = 0, storageError = 0;
        for (int i = 0; i < carCheckList.size(); i++) {
            boolean isError = false;
            ChecklistOfCar checklistOfCar = carCheckList.get(i);
            if (checklistOfCar.getIsCarOutStatusError().equals(1)) {
                isError = true;
                carStatusError++;
                CarAlertInfo carAlertInfo = CarAlertInfo.builder().carId(checklistOfCar.getCarId())
                        .alertType(AlertTypeEnum.CarOutStatusError.getKey()).alertData("1").build();
                carAlertInfoList.add(carAlertInfo);
            }
            if (checklistOfCar.getIsGNSSError().equals(1)) {
                isError = true;
                gnssError++;
                CarAlertInfo carAlertInfo = CarAlertInfo.builder().carId(checklistOfCar.getCarId())
                        .alertType(AlertTypeEnum.GNSSError.getKey()).alertData(String.valueOf(checklistOfCar.getGNSSErrorTimes())).build();
                carAlertInfoList.add(carAlertInfo);
            }
            if (checklistOfCar.getIsLidarError().equals(1)) {
                isError = true;
                lidarError++;
                CarAlertInfo carAlertInfo = CarAlertInfo.builder().carId(checklistOfCar.getCarId())
                        .alertType(AlertTypeEnum.LidarError.getKey()).alertData(String.valueOf(checklistOfCar.getLidarErrorTimes())).build();
                carAlertInfoList.add(carAlertInfo);
            }
            if (checklistOfCar.getIsCameraError().equals(1)) {
                isError = true;
                cameraError++;
                CarAlertInfo carAlertInfo = CarAlertInfo.builder().carId(checklistOfCar.getCarId())
                        .alertType(AlertTypeEnum.CameraError.getKey()).alertData(String.valueOf(checklistOfCar.getCameraErrorTimes())).build();
                carAlertInfoList.add(carAlertInfo);
            }
            if (checklistOfCar.getStorageDiskCap() < 500) {
                isError = true;
                storageError++;
                CarAlertInfo carAlertInfo = CarAlertInfo.builder().carId(checklistOfCar.getCarId())
                        .alertType(AlertTypeEnum.CarStorageDiskLow.getKey()).alertData(String.valueOf(checklistOfCar.getStorageDiskCap())).build();
                carAlertInfoList.add(carAlertInfo);
            }
            if (checklistOfCar.getCloudCarStatus().equals(1)) {
                carOutNum++;
            }
            if (isError) {
                carErrorNum++;
            }
        }
        CarStatistic carStatistic = CarStatistic.builder().carOutNum(carOutNum).carErrorNum(carErrorNum).build();
        //异常统计
        fetch.setCarStatistic(carStatistic);
        //异常告警
        fetch.setCarAlertInfoList(carAlertInfoList);
        //异常分布
        AlertDistribute alertDistribute = AlertDistribute.builder().carStatusError(carStatusError).gnssError(gnssError)
                .lidarError(lidarError).cameraError(cameraError).storageDiskError(storageError).build();
        fetch.setAlertDistribute(alertDistribute);
        //推送统计
        double push2ThirdPartyTotal = operationLogDao.count(new Query(Criteria.where("title").is("推送中海").and("operate_date").gte(MyDateUtil.getStartOfDay(new Date()))));
        double push2ThirdPartySuccess = operationLogDao.count(new Query(Criteria.where("title").is("推送中海").and("type").is("info").and("operate_date").gte(MyDateUtil.getStartOfDay(new Date()))));
        double successRate1 = push2ThirdPartyTotal == 0 ? 0 : FormatUtil.keepDecimalsOfK(push2ThirdPartySuccess / push2ThirdPartyTotal, 2);
        double push2YtTotal = operationLogDao.count(new Query(Criteria.where("title").is("中海回调").and("operate_date").gte(MyDateUtil.getStartOfDay(new Date()))));
        double push2YtSuccess = operationLogDao.count(new Query(Criteria.where("title").is("中海回调").and("type").is("info").and("operate_date").gte(MyDateUtil.getStartOfDay(new Date()))));
        double successRate2 = push2YtTotal == 0 ? 0 : FormatUtil.keepDecimalsOfK(push2YtSuccess / push2YtTotal, 2);
        pushStatistic.setPush2ThirdPartyFail((int) (push2ThirdPartyTotal - push2ThirdPartySuccess));
        pushStatistic.setPush2ThirdPartySuccess((int) push2ThirdPartySuccess);
        pushStatistic.setPush2ThirdPartySuccessRate(successRate1);
        pushStatistic.setPush2YtFail((int) (push2YtTotal - push2YtSuccess));
        pushStatistic.setPush2YtSuccess((int) push2YtSuccess);
        pushStatistic.setPush2YtSuccessRate(successRate2);

        checklistDao.saveOrUpdate(fetch);
        log.info("推送统计耗时： {} ms", System.currentTimeMillis() - start);
    }

    public String getAuthorization(String username, String token) {
        //使用base64进行加密
        byte[] tokenByte = Base64.getEncoder().encode((username + ":" + token).getBytes());
        //将加密的信息转换为string
        String tokenStr = new String(tokenByte);
        return "Basic " + tokenStr;
    }

    public Checklist initChecklist() {
        Checklist checklist = new Checklist();
        checklist.setDate(dateStr);

        Checklist yesterdayFetch = checklistDao.fetch(yesterdayDateStr);
        List<Car> allCars = carDao.findAll();
        List<ChecklistOfCar> checklistOfCars = new ArrayList<>();
        //初始化carCheckList
        allCars.stream().forEach(car -> {
            List<ChecklistOfCar> yesterdayChecklist = null;
            Double yesterdayStorageCap = 0d;
            ChecklistOfCar yesterdayCheck = null;
            if (ObjectUtils.isNotEmpty(yesterdayFetch)) {
                yesterdayChecklist = yesterdayFetch.getCarList();
                Optional<ChecklistOfCar> yesterdayCheckOptional = yesterdayChecklist.stream().filter(yesterday -> yesterday.getCarId().equals(car.getId())).findFirst();
                if (yesterdayCheckOptional.isPresent()) {
                    yesterdayCheck = yesterdayCheckOptional.get();
                }
            }
            yesterdayStorageCap = (yesterdayCheck == null || yesterdayCheck.getStorageDiskCap() == null) ? 0 : yesterdayCheck.getStorageDiskCap();
            ChecklistOfCar todayCheck = ChecklistOfCar.builder().carId(car.getId()).plateNo(car.getPlateNo()).date(dateStr).version("0.0")
                    .sdkCarTotalMinute(0).cloudCarTotalMinute(0).mergeNum(0l).sdkCarStatus(0).cloudCarStatus(0).isCarOutStatusError(0)
                    .isGNSSError(0).isLidarError(0).isCameraError(0).storageDiskCap(yesterdayStorageCap)
                    .gNSSErrorTimes(0).lidarErrorTimes(0).cameraErrorTimes(0).miles(0d)
                    .hitNum(0l).uncheckHitNum(0l).checkRate(0d).pushThirdNum(0l).passHitNum(0l).passRate(0d).build();
            checklistOfCars.add(todayCheck);
        });
        checklist.setCarList(checklistOfCars);
        //初始化推送统计
        checklist.setPushStatistic(PushStatistic.builder().date(dateStr)
                .push2ThirdPartySuccess(0).push2ThirdPartyFail(0).push2ThirdPartySuccessRate(0d)
                .push2YtSuccess(0).push2YtFail(0).push2YtSuccessRate(0d).build());
        //初始化总数统计
        checklist.setTotalStatistic(TotalStatistic.builder().totalRuntime(0d).totalNewHits(0)
                .totalMiles(0d).totalCheckHits(0).build());
        return checklist;
    }

    /**
     * 每天23点将数据库数据导入excel
     *
     * @return
     */
//    @Scheduled(cron = "0 0 23 * * ?")
    public String exportExcel() {
        Date date = new Date();
        Checklist fetch = checklistDao.fetch(dateStr);
        if (ObjectUtils.isEmpty(fetch)) {
            return "导入失败，数据库数据为空";
        }
        int dayOfMonth = MyDateUtil.getDayOfMonth(date);
        String filePath = dirPath + String.format("Checklist-%s.xlsx", dateStr);
        ExcelUtil.exportCollections2Excel(dirPath, filePath, fetch, "carList", dayOfMonth);
        ExcelUtil.exportObject2Excel(dirPath, filePath, fetch.getPushStatistic(), dayOfMonth);
        log.info("数据成功写入excel文件");
        return "导入成功， 表格路径为： " + filePath;
    }

    /**
     * 每天早上十点钉钉群推送消息
     */
    @Scheduled(cron = "0 0 10 * * ?")
    public void dingTalk() {
        //找出昨天的巡检报表
        String date = MyDateUtil.dateFormatStr(DateUtils.addDays(new Date(), -1), "yyyy-MM-dd");
        String content = getChecklistContent(date);
        String title = date + " 巡检报表";
        DingDingMsgSendUtil.sendDingDingActionCardGroupMsg(tokenId, secret, title, content, "查看巡检详情", downloadUrl);
    }

    public String getChecklistContent(String date) {
        HashMap<String, String> map = new HashMap<>();
        Checklist fetch = checklistDao.fetch(date);
        if (ObjectUtils.isEmpty(fetch)) {
            throw new RuntimeException("数据异常");
        }
        List<ChecklistOfCar> checklistOfCarList = fetch.getCarList();
        long carOutNum = checklistOfCarList.stream().filter(car -> car.getCloudCarStatus().equals(1)).count();
        List<ChecklistOfCar> list = checklistOfCarList.stream().filter(car -> car.getIsCarOutStatusError().equals(1) || car.getIsGNSSError().equals(1) ||
                car.getIsLidarError().equals(1) || car.getIsCameraError().equals(1) || car.getStorageDiskCap() < 500).collect(Collectors.toList());
        long errorNum = list.size();
        String checklistPattern =
                "### 巡检日期 " + date + "\n" +
                        "---\n" +
                        "- 当日出车： %s辆\n" +
                        "- 异常车辆： %s辆\n";
        String content = String.format(checklistPattern, carOutNum, errorNum);
        if (errorNum == 0) {
            content += "- 异常车牌： 无";
        } else {
            content += "- 异常车牌： \n";
            for (ChecklistOfCar car : list) {
                String extra = "";
                if (car.getIsCarOutStatusError().equals(1)) {
                    extra += "  出车状态异常";
                }
                if (car.getIsGNSSError().equals(1)) {
                    extra += "  GNSS异常";
                }
                if (car.getIsLidarError().equals(1)) {
                    extra += "  雷达异常";
                }
                if (car.getIsCameraError().equals(1)) {
                    extra += "  摄像头异常";
                }
                if (car.getStorageDiskCap() < 500) {
                    extra += "  数据盘剩余容量 " + car.getStorageDiskCap() + "GB";
                }
                content += "  - " + car.getPlateNo() + extra + "\n";
            }
        }
        return content;
    }

    /**
     * 自动清理excel报表
     */
    @Scheduled(cron = "0 30 23 * * ?")
    public void timingCleanup() {
        String suffix = dateStr + ".xlsx";
        File dir = new File(dirPath);
        File[] files = dir.listFiles();
        String weekAgo = String.format("Checklist-%s.xlsx", MyDateUtil.dateFormatStr(DateUtils.addDays(new Date(), -7), "yyyy-MM-dd"));
        log.info(weekAgo);
        for (int i = 0; i < files.length; i++) {
            String fileName = files[i].getName();
            if (fileName.compareTo(weekAgo) <= 0) {
                files[i].delete();
                log.info("巡检报表: {}, 删除成功", fileName);
            }
        }
    }

    public void processAlert(AlertRequest prometheusAlert) {
        Date now = new Date();
        String date = MyDateUtil.dateFormatStr(now, "yyyy-MM-dd");
        if ("firing".equals(prometheusAlert.getStatus())) {
            prometheusAlert.getAlerts().stream().forEach(alert -> {
                AlertLabel labels = alert.getLabels();
                String alertName = labels.getAlertname();
                if (blackListAlertName.contains(alertName)) {
                    log.info("暂时不支持处理此类告警: {}", alertName);
                    return;
                }
                //判断异常是否是在维修期间推送的
                long count = carMaintainDao.count(new Query(Criteria.where("car_id").is(labels.getInstance())
                        .and("create_time").gte(MyDateUtil.getStartOfDay(now).getTime()).lte(now.getTime())));
                if (count > 0) {
                    log.info("该车辆处于维修状态, 告警无效!");
                    return;
                }
                Query query = new Query(Criteria.where("car_id").is(labels.getInstance()).and("date").is(date).and("alertname").is(labels.getAlertname()));
                AlertDTO alertDTO = alertDao.findOne(query);
                if (ObjectUtils.isEmpty(alertDTO)) {
                    alertDTO = AlertDTO.builder().carId(labels.getInstance()).date(date).alertTimeHistory(Arrays.asList(now.getTime() / 1000))
                            .times(1).alertname(AlertTypeEnum.valueOf(labels.getAlertname())).build();
                } else {
                    Integer times = alertDTO.getTimes();
                    List<Long> alertTimeHistory = alertDTO.getAlertTimeHistory();
                    alertTimeHistory.add(now.getTime() / 1000);
                    alertDTO.setTimes(++times);
                    alertDTO.setAlertTimeHistory(alertTimeHistory);
                }
                alertDao.saveOrUpdate(alertDTO);
            });
        }
    }

    public MonitorResponse monitor(String date) {
        MonitorResponse monitorResponse = new MonitorResponse();

//        if (date.compareTo(dateStr) >= 0) {
//            AlertDistribute alertDistribute = AlertDistribute.builder().carStatusError(0)
//                    .gnssError(0).lidarError(0).cameraError(0).storageDiskError(0).build();
//            monitorResponse.setAlertDistribute(alertDistribute);
//            return monitorResponse;
//        }
        Checklist fetch = checklistDao.fetch(date);
        if (ObjectUtils.isEmpty(fetch)) {
            return monitorResponse;
        }
        //异常统计
        monitorResponse.setCarStatistic(fetch.getCarStatistic());
        //异常分布
        monitorResponse.setAlertDistribute(fetch.getAlertDistribute());
        //总数统计
        monitorResponse.setTotalStatistic(fetch.getTotalStatistic());
        //推送统计
        monitorResponse.setPushStatistic(fetch.getPushStatistic());
        //异常告警 车辆监测 业务监测 事件监测
        convertChecklist(fetch, monitorResponse);
        return monitorResponse;
    }

    public void convertChecklist(Checklist fetch, MonitorResponse monitorResponse) {
        List<CarAlertInfoVO> carAlertInfoVOList = new ArrayList<>();
        List<CarMonitorDetail> carMonitorDetail = new ArrayList<>();
        List<BusinessMonitorResponse> businessMonitorResponses = new ArrayList<>();
        //异常告警
        List<CarAlertInfo> carAlertInfoDTOList = fetch.getCarAlertInfoList();
        if (ObjectUtils.isNotEmpty(carAlertInfoDTOList)) {
            Map<String, List<CarAlertInfo>> carAlertMap = carAlertInfoDTOList.stream().collect(Collectors.groupingBy(i -> i.getCarId()));
            carAlertMap.forEach((carId, alerts) -> {
                Car car = carMap.get(carId);
                Company company = companyMap.get(car.getHandleCompany());
                CarAlertInfoVO carAlertInfoVO = CarAlertInfoVO.builder().companyId(car.getHandleCompany()).
                        companyName(company.getName()).plateNo(car.getPlateNo()).alertDetail(alerts).build();
                carAlertInfoVOList.add(carAlertInfoVO);
            });
        }
        List<ChecklistOfCar> carList = fetch.getCarList();
        carList.stream().forEach(checklist -> {
            Car car = carMap.get(checklist.getCarId());
            Company company = companyMap.get(car.getHandleCompany());
            // 车辆监测
            CarMonitorDetail monitorDetail = CarMonitorDetail.builder().id(IdGeneratorUtil.getUUID()).companyId(company.getId()).companyShortName(company.getShortName())
                    .plateNo(car.getPlateNo()).version(checklist.getVersion())
                    .sdkCarStatus(checklist.getSdkCarStatus().equals(0) ? "未出" : "出车").cloudCarStatus(checklist.getCloudCarStatus().equals(0) ? "未出" : "出车")
                    .isSDKError(checklist.getIsCarOutStatusError().equals(0) ? "N" : "Y").storageDiskCap(String.valueOf(checklist.getStorageDiskCap()))
                    .isGNSSError(checklist.getIsGNSSError().equals(0) ? "N" : "Y").gNSSErrorTimes(checklist.getGNSSErrorTimes())
                    .isLidarError(checklist.getIsLidarError().equals(0) ? "N" : "Y").lidarErrorTimes(checklist.getLidarErrorTimes())
                    .isCameraError(checklist.getIsCameraError().equals(0) ? "N" : "Y").cameraErrorTimes(checklist.getCameraErrorTimes())
                    .startTime(checklist.getStartTime()).endTime(checklist.getEndTime()).sdkCarTotalMinute(checklist.getSdkCarTotalMinute())
                    .cloudCarTotalMinute(checklist.getCloudCarTotalMinute()).miles(checklist.getMiles()).build();
            carMonitorDetail.add(monitorDetail);
            //业务监测
            BusinessMonitorResponse businessResponse = BusinessMonitorResponse.builder().id(IdGeneratorUtil.getUUID()).companyId(company.getId()).companyShortName(company.getShortName()).plateNo(car.getPlateNo())
                    .mergeNum(checklist.getMergeNum()).hitNum(checklist.getHitNum()).uncheckHitNum(checklist.getUncheckHitNum())
                    .checkRate(checklist.getCheckRate()).pushThirdNum(checklist.getPushThirdNum()).passHitNum(checklist.getPassHitNum())
                    .passRate(checklist.getPassRate()).totalNewHitNum(checklist.getTotalNewHitNum()).totalPassHitNum(checklist.getTotalPassHitNum()).build();
            businessMonitorResponses.add(businessResponse);

        });
        //事件监测
        List<ChecklistOfEvent> eventList = fetch.getEventList();
        eventList.forEach(event -> {
            event.setId(IdGeneratorUtil.getUUID());
        });
        //车辆状态
        carMonitorDetail.stream().forEach(carMonitor -> {
            if (carMonitor.getIsSDKError().equals("Y") || carMonitor.getIsGNSSError().equals("Y") || carMonitor.getIsLidarError().equals("Y")
                    || carMonitor.getIsCameraError().equals("Y") || Double.parseDouble(carMonitor.getStorageDiskCap()) < 500) {
                //状态异常
                carMonitor.setStatus("E");
            } else if (carMonitor.getCloudCarStatus().equals("出车")) {
                //出车
                carMonitor.setStatus("O");
            } else {
                //未出车
                carMonitor.setStatus("P");
            }
        });
        //排序
        List<CarMonitorDetail> sortedCarMonitorDetail = carMonitorDetail.stream().sorted(Comparator.comparing(CarMonitorDetail::getStatus)).collect(Collectors.toList());
        List<BusinessMonitorResponse> sortedBusinessResponses = businessMonitorResponses.stream().sorted(Comparator.comparingLong(BusinessMonitorResponse::getHitNum).reversed()).collect(Collectors.toList());
        List<ChecklistOfEvent> sortedEventList = eventList.stream().sorted(Comparator.comparingInt(ChecklistOfEvent::getTotal).reversed()).collect(Collectors.toList());
        monitorResponse.setCarAlertInfo(carAlertInfoVOList);
        monitorResponse.setCarMonitorDetail(sortedCarMonitorDetail);
        monitorResponse.setBusinessMonitorResponse(sortedBusinessResponses);
        monitorResponse.setEventMonitorResponse(sortedEventList);
    }

    public Map<String, String> getAlertType() {
        Map<String, String> map = new HashMap<>();
        Arrays.asList(AlertTypeEnum.values()).forEach(alertTypeEnum -> {
            map.put(alertTypeEnum.getKey(), alertTypeEnum.getValue());
        });
        return map;
    }
}
